package edu.kit.aifb.ldstream.learner.tasks.mod;

import java.util.Set;
import java.util.TreeSet;

import org.openrdf.model.BNode;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.rio.ParserConfig;
import org.openrdf.rio.RDFParser.DatatypeHandling;

import edu.kit.aifb.ldstream.env.Environment;
import edu.kit.aifb.ldstream.learner.query.InstanceCache;
import edu.kit.aifb.ldstream.learner.util.URIComparator;

public class CoherenceTask extends LearnerTask{

	private ModStatistics stats;
	private Set<Type> typeSet;

	public CoherenceTask(ModStatistics stats, Set<Type> typeSet){
		this.stats = stats;
		this.typeSet = typeSet;
	}

	public void run(){

		startup();
		setPreserverBnodes();

		if(Environment.LOGGING) Environment.log.info("Started to learn the Coherence.");
		double structuredness = computeCoherence();
		this.stats.setStructuredness(structuredness);
		if(Environment.LOGGING) Environment.log.info("Finished learning the Coherence.");

		shutdown();
	}

	private double computeCoherence(){
		/*
		 * The calculating the coherence according to the Apples & Oranges Paper
		 */
		double ch = 0.0d;

		// intermediate results, should not be to big because of limited types
		double[] cv = new double[this.typeSet.size()];

		int[] numInstance = new int[this.typeSet.size()];
		int[] numProps = new int[this.typeSet.size()];

		Set<URI> properties;
		InstanceCache ic;
		int i = 0;
		//overall number of properties and instances
		int overall = 0;
		int oc;
		for(Type t : this.typeSet){
			ic = new InstanceCache(t);
			properties = new TreeSet<URI>(new URIComparator());
			// collecting the properties and returning the number of instances |I(T)| (saves some time)
			numInstance[i] = collectProperties(ic, properties);
			overall += numInstance[i];
			// |P(T,D)|
			numProps[i] = properties.size();
			overall += properties.size();
			///sum_overall_p[OC(p, I(T,D))]
			oc = oc(ic, properties);
			//sum_overall_p[oc(p,I(T,D)] *  1/(|P(T)| * |I(T,D)|)
			cv[i] = ((double) oc) / ( (double) numProps[i] * (double) numInstance[i] );
			
			i++;
			// free memory
			ic.close();
		}
		// at this point we've computed the coverage and all essentials for the weight and coverage
		double currentWT;
		for(int j = 0; j < this.typeSet.size(); j++){
			currentWT = ((double) numInstance[j] + (double) numProps[j]) / (double) overall;
			ch += currentWT * cv[j];
		}
		return ch;
	}
	

	private int oc(InstanceCache ic, Set<URI> properties){
		int oc = 0;
		for(URI instance : ic){
			for(URI property : properties){
				if(hasProperty(instance, property)){
					oc++;
				}
			}
		}
		return oc;
	}

	private boolean hasProperty(Resource subject, URI predicate){
		boolean propertyFound = false;
		try {
			propertyFound = con.hasStatement(subject, predicate, null, false);
			if(!propertyFound){
				RepositoryResult<Statement> result = this.con.getStatements(subject, null, null, false);
				Statement s;
				try{
					while(result.hasNext() && !propertyFound){
						s = result.next();
						if(s.getObject() instanceof BNode){
							propertyFound = hasProperty((BNode) s.getObject(), predicate);
						}
					}
				} finally{
					result.close();
				}
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
		return propertyFound;
	}

	private int collectProperties(InstanceCache ic, Set<URI>properties){
		int numInstances = 0;

		for(URI instance : ic){
			numInstances++;
			collectProperties(instance, properties);
		}
		return numInstances;
	}

	private void collectProperties(Resource subject, Set<URI> properties){
		RepositoryResult<Statement> result = null;

		try {
			result = this.con.getStatements(subject, null, null, false);
			try{
				Statement s;
				while(result.hasNext()){
					s = result.next();
					properties.add(s.getPredicate());
					if(s.getObject() instanceof BNode){
						collectProperties((BNode) s.getObject(), properties);
					}
				}
			} finally{
				result.close();
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
	}

	private void setPreserverBnodes(){
		this.con.setParserConfig(new ParserConfig(true, true, true, DatatypeHandling.VERIFY));
	}
}
